const { add } = require('lodash')
const fp = require('lodash/fp')
const { Maybe, Container } = require('./support')

/**
 * 1. setTimeout代码改成Promise
 */

const promiseGenerate = val => {
  return new Promise(resovle => {
    setTimeout(() => {
      resovle(val)
    }, 10)
  })
}

promiseGenerate('hello')
  .then(res => {
    return promiseGenerate('lagou').then(data => res + data)
  })
  .then(res => {
    return promiseGenerate('I ❤ U').then(data => res + data)
  })
  .then(console.log)


/**
 * 2. fp模块
 */
// 数据：horsepower 马力，dollar_value 价格，in_stock 库存
const cars = [
  { name: 'Ferrari FF', horsepower: 660, dollar_value: 700000, in_stock: true },
  { name: 'Spyker C12 Zagato', horsepower: 650, dollar_value: 648000, in_stock: false },
  { name: 'Jaguar XKR-S', horsepower: 550, dollar_value: 132000, in_stock: false },
  { name: 'Audi R8', horsepower: 525, dollar_value: 114200, in_stock: false },
  { name: 'Aston Martin One-77', horsepower: 750, dollar_value: 1850000, in_stock: true },
  { name: 'Pagani Huayra', horsepower: 700, dollar_value: 1300000, in_stock: false }
]


/**
 * 2.1 fp.flowRight
 */
const isLastInStock = fp.flowRight(fp.prop('in_stock'), fp.last)

console.log(isLastInStock(cars))

/**
 * 2.2 使用fp.flowRight()、fp.prop()、fp.first()获取一个car的name
 */
const getFirstCarName = fp.flowRight(fp.prop('name'), fp.first)

console.log(getFirstCarName(cars))


/**
 * 2.3 使用帮助函数 _average 重构 averageDollarValue
 *      使用函数组合的方式
 */
let _average = function (xs) {
  return fp.reduce(fp.add, 0, xs) / xs.length
}

const averageDollarValue = fp.flowRight(_average, fp.map(car => car.dollar_value))

console.log(averageDollarValue(cars))


/**
 * 2.4 sanitizeNames，实现sanitizeNames(['Hello Work']) => ['hello_world']
 */
let _underscore = fp.replace(/\W+/g, '_')

const sanitizeNames = fp.map(
  fp.flowRight(
    _underscore,
    fp.lowerCase,
    v => v.name
  )
)

console.log(sanitizeNames(cars))


/**
 * 3.
 */


/**
 * 3.1 使用fp.add(x, y) 和 fn.map(f, x)创建一个能让functor里的值增加的函数ex1
 */
let maybe = Maybe.of([5, 6, 1])

let ex1 = (num) => {
  return maybe.map(i => fp.map(fp.add(num), i))
}

console.log(ex1(2))


/**
 * 3.2 实现ex2能够使用fp.first获取列表的第一个元素
 */
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])

let ex2 = () => {
  return xs.map(fp.first)._value
}

console.log(ex2())

/**
 * 3.3 实现一个函数ex3, 使用safeProp和fp.first找到user的名字的首字母
 */
let safeProp = fp.curry(function(x, o) {
  return Maybe.of(o[x])
})

let user = { id: 2, name: 'Albert' }

let ex3 = () => {
  return fp.flowRight(fp.first, x => x._value, safeProp('name'))(user)
}

console.log(ex3())

/**
 * 3.4 使用Maybe重写ex4，不要有if语句
 */
let ex4 = n => Maybe.of(n).map(parseInt)._value

console.log(ex4(''))


/**
 * 4. 手写Promise源码
 */

// Promise中三个状态，等待、成功、失败
const PENDING = 'pending'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
  constructor(executor) {
    // 使用构造函数创建时Promise对象，传入的执行器会立刻执行
    // 错误处理
    try {
      executor(this.resolve, this.reject)
    } catch(e) {
      this.reject(e)
    }
  }

  value = undefined // 成功返回值
  reason = undefined // 失败原因

  // 状态
  status = PENDING

  // 存储成功回调和失败回调，供异步回调resovle或reject使用
  successCallback = []
  failCallback = []

  /**
   * resolve
   * @returns 
   * 更改状态，只能从pending -> fulfilled
   * 存储成功返回值
   */
  resolve = (value) => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value

    // 查看成功回调数组中是否还有值，有则调用
    while (this.successCallback.length) {
      this.successCallback.shift()()
    }
  }

  /**
   * reject
   * @returns 
   * 更改状态，只能从pending -> rejected
   * 存储失败原因
   */
  reject = (reason) => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason

    // 查看失败回调数组中是否还有值，有则调用
    while (this.failCallback.length) {
      this.failCallback.shift()()
    }
  }

  /**
   * then 方法
   * @param {Function} successCallback 成功回调，参数是成功的返回值
   * @param {Function} failCallback 失败回调，参数是失败的原因
   * 1. 参数是可选参数，但除了函数之外都会忽略
   * 2. 返回值是promise对象
   * 3. 下一个then成功回调的参数是上一个then成功回调的返回值
   * 4. 判断then回调的返回值 --- handleReturnValue
   *      如果是普通值，则直接调用resolve
   *      如果是Promise对象，根据Promise对象返回的结果决定调用resolve还是reject
   * 5. 如果then回调函数返回值是自身Promise对象，则会抛异常
   * 6. then的回调是异步回调
   */
  then(successCallback, failCallback) {
    successCallback = typeof successCallback === 'function' ? successCallback : v => v
    failCallback = typeof failCallback === 'function' ? failCallback : reason => { throw reason }

    let promise = new Promise((resolve, reject) => {
      // 封装函数处理返回值和异常
      const handleResult = (func, value) => {
        // 使用setTimeout模拟异步情况
        setTimeout(() => {
          try {
            let res = func(value)
            handleReturnValue(promise, res, resolve, reject)
          } catch(e) {
            reject(e)
          }
        }, 0)
      }

      // 判断状态
      if (this.status === FULFILLED) {
        // 调用成功回调
        handleResult(successCallback, this.value)
      } else if (this.status === REJECTED) {
        // 调用失败回调
        handleResult(failCallback, this.reason)
      } else {
        // 状态未更改，说明发生异步情况，将成功回调和失败回调存储起来
        // 实例化的promise可以多次调用then，即可能有多个成功回调和失败回调
        this.successCallback.push(() => {
          handleResult(successCallback, this.value)
        })
        this.failCallback.push(() => {
          handleResult(failCallback, this.reason)
        })
      }
    })
    return promise
  }

  /**
   * Promise.resolve
   * @param {*} value 
   * 返回promise对象
   * 参数：
   *  参数是普通值，调用resolve返回Promise对象
   *  参数是Promise对象，返回值这个Promise对象
   */
  static resolve (value) {
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }

  /**
   * Promise.reject
   * @param {*} value
   * 不管接受什么结果，都直接调用reject抛异常
   */
  static reject (value) {
    return new Promise((resolve, reject) => reject(val))
  }

  /**
   * 捕获异常
   * 返回promise对象
   * 相当于调用then方法，但成功回调undefined，callback对应失败回调
   */
  catch (callback) {
    return this.then(undefined, callback)
  }

  /**
   * 无论成功还是失败都会执行
   * 返回promise对象
   */
  finally (callback) {
    return this.then(value => {
      // 防止callback是异步代码，将其用Promsie.resolve处理返回promise对象
      return MyPromise.resolve(callback()).then(() => value)
    }, reason => {
      return MyPromise.resolve(callback()).then(() => { throw reason })
    })
  }

  /**
   * Promise.all
   * @param {*} array
   * 参数数组可以包含普通值或promise对象
   * 返回值是Promise对象
   * 只要有一个异常，则直接抛出异常
   * 只有全部正常，才会正确返回
   */
  static all (array) {
    let result = []

    // 处理array数组中有promise异步回调的情况
    let count = 0

    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        count++
        result[key] = value
        if (count === array.length) resolve(result)
      }

      for(let i = 0; i < array.length; i++) {
        let item = array[i]

        if (item instanceof MyPromise) {
          // promise对象
          item.then(value => addData(i, value), reason => reject(reason))

        } else {
          // 普通值
          addData(i, item)
        }
      }
    })
  }
}

/**
 * 处理失败回调或成功回调中的返回值
 * @param {*} promise 
 * @param {*} x 
 * @param {*} resovle 
 * @param {*} reject 
 */
function handleReturnValue(promise, x, resolve, reject) {
  if (promise === x) {
    return reject(new Error('Chaining cycle detected for promise #<Promise>'))
  }

  if (x instanceof MyPromise) {
    // Promise对象
    x.then(resolve, reject)
  } else {
    // 普通值
    resolve(x)
  }
}